package dcedit.doc;

// Import Java Tools Classes
import com.sun.javadoc.*;
import dcedit.util.Constants;
// Import Java SDK Classes
import java.io.*;
import java.util.*;

/**
 * DefaultDoclet class<br/>
 * This is the default javadoc doclet for DCEdit.<br/>
 * It can be used to generate javadocs for any java project.<br/>
 * This javadoc depends on dojo version 1.6, as hosted on Google's content delivery network.
 * @author dca
 */
public class DefaultDoclet {
   /** File output writer */
   private static BufferedWriter fout;

   /** Output path file name string */
   private static String outputFilePath = File.separator; // default path

   /** Output path file name string */
   private static String outputFileName = "DCEdit-api.htm"; // default name

   /**
    * optionLength method<br/>
    * This method returns 2 or 0, specifying how many arguments are supplied with a given javadoc option
    * @param option Used by the javadoc routine to determine the number of arguments for options
    * @return int The number arguments for a single option
    */
   public static int optionLength(String option) {
      if(option.equals("-d")) {
         return 2;
      } else {
         return 0;
      } // END if/else
   } // END optionLength method

   /**
    * start static method<br/>
    * This method is executed when the javadoc routine is executed.<br/>
    * First, it checks for an output file javadoc option.<br/>
    * Then, it opens a file stream and starts creating the javadoc contents into that file.
    * @param root The root object of the javadoc routine
    * @return boolean true is returned by default
    */
   public static boolean start(RootDoc root) {
      // Check command-line options
      for(String[] x : root.options()) {
         boolean foundOutputPathFlag = false;

         // Loop through all javadoc routine option arguments
         for(String y : x) {
            // This argument means that an output file directory was supplied
            if("-d".equals(y)) {
               foundOutputPathFlag = true;
            } else if(foundOutputPathFlag) {
               outputFilePath = y;
               foundOutputPathFlag = false;
            } // END if/else
         } // END loop
      } // END loop

      // Make sure the output path ends with a file separator
      if(!outputFilePath.endsWith(File.separator)) outputFilePath += File.separator;

      // Define the output file writer
      try {
         System.out.println("Creating file writer: " + outputFilePath + outputFileName);
         fout = new BufferedWriter(new FileWriter(outputFilePath + outputFileName));
         System.out.println("Success.");
      } catch(IOException ioe) {
         System.out.println("Error: File Write Error: " + ioe.getMessage());
      } catch(Exception unknownExc) {
         System.out.println("Error: Unknown Error: " + unknownExc.getMessage());
      } // END try/catch

      out("<html>");
      out("<head>");
      processStyleSheet();
      processScripts();
      out("</head>");
      out("<body class='claro'>");

      // Begin Tab Tab Container
      out("<div id='mainDocContainer' dojoType='dijit.layout.TabContainer' style='width: 100%; height: 600px;' tabPosition='top' tabStrip='true'>");

      // For each class
      TreeSet<ClassDoc> classSet = new TreeSet<ClassDoc>();
      classSet.addAll(Arrays.asList(root.classes()));
      for(ClassDoc clazz : classSet) {
         // Begin Content Pane
         final String clazzID = clazz.name().replace(".","");
         out("   <div class='singleDocContainer' dojoType='dijit.layout.ContentPane' title='" + clazzID + "'>");

         out("      <table cellpadding='1' cellspacing='0' class='classTable' width='100%'>");
         out("         <tr align='center' valign='top' class='classRow'>");
         out("            <td class='classCell'>");
         out("               <span id='" + clazzID + "'>");
         out("                  " + clazz.qualifiedName());
         out("               </span>");
         out("               <div dojoType='dijit.Tooltip' connectId='" + clazzID + "'>");
         out("                  " + clazz.commentText());
         for(Tag tag : clazz.tags()) {
            out("                  <br/>" + tag.name() + " - " + tag.text());
         } // END tag loop
         out("               </div>");
         out("            </td>");
         out("         </tr>");
         out("      </table>");

         out("      <table cellpadding='1' cellspacing='0' class='classTable'>");
         out("         <tr align='left' valign='top'>");
         out("            <td>");
         processFields(clazz.fields());
         out("            </td>");
         out("            <td>");
         processMethods(clazz.methods());
         out("            </td>");
         out("            <td>");
         processConstructors(clazz.constructors());
         out("            </td>");
         out("         </tr>");
         out("      </table>");

         // END Content Pane
         out("   </div>");
      } // END class loop

      // END Tab Container
      out("</div>");

      out("</body>");
      out("</html>");

      closeWriter(fout);
      return true;
   } // END static start method

   /**
    * processStyleSheet static method<br/>
    * This method outputs the style segment of the javadoc.<br/>
    * This javadoc depends on dojo version 1.6 claro.
    */
   private static void processStyleSheet() {
      out("   <style type='text/css'>");
      out("      @import url('http://ajax.googleapis.com/ajax/libs/dojo/1.6/dijit/themes/claro/claro.css');");
      out("      @import url('DCEdit-api.css');");
      out("   </style>");
   } // END processStyleSheet static method

   /**
    * processScripts static method<br/>
    * This method outputs the javascript segments of the javadoc.<br/>
    * This javadoc depends on dojo version 1.6.
    */
   private static void processScripts() {
      out("   <script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojo/dojo.xd.js' djConfig='parseOnLoad: true'></script>");
      out("   <script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js'></script>");
      out("   <script type='text/javascript'>");
      // Include dojo widgets
      out("      dojo.require('dijit.layout.TabContainer');");
      out("      dojo.require('dijit.layout.ContentPane');");
      out("      dojo.require('dijit.Tooltip');");
      // Define window/frame resize function
      out("      function resizeFrames() {");
      out("         var winHeight = $(window).height();");
      out("         var docHeight = winHeight - 50;");
      out("         $('#mainDocContainer').css('height', docHeight);");
      out("      } // END function resizeFrames");

      out("      dojo.addOnLoad(resizeFrames);");
      out("      dojo.addOnLoad(function() {");
      out("         dojo.connect(window, 'onresize', resizeFrames);");
      out("      });");
      out("   </script>");
   } // END processScripts static method

   /**
    * processFields static method<br/>
    * This method outputs all the field information for a single java class, in pseudo-UML style.
    * @param fields An array of all the fields for a single java class
    */
   private static void processFields(FieldDoc[] fields) {
      if(null != fields) {
         // Define the field table
         out("               <table cellpadding='1' cellspacing='0' class='fieldTable'>");
         // If there are fields
         if(fields.length > 0) {
            out("                  <tr align='left' valign='top' class='fieldHeadingRow'>");
            out("                     <td colspan='3' class='fieldHeadingCell'>");
            out("                        Fields");
            out("                     </td>");
            out("                  </tr>");
         } // END if there are fields

         // For each field
         TreeSet<FieldDoc> fieldSet = new TreeSet<FieldDoc>();
         fieldSet.addAll(Arrays.asList(fields));
         for(FieldDoc field : fieldSet) {
            final String fieldID = field.containingClass().name().replace(".", "") + "_" + field.name().replace(".", "");
            out("                  <tr align='left' valign='top' class='fieldRow'>");
            out("                     <td class='fieldAccessCell'>");
            out("                        " + processModifiers(field));
            out("                     </td>");
            out("                     <td class='fieldTypeCell'>");
            out("                        " + field.type().typeName() + field.type().dimension());
            out("                     </td>");
            out("                     <td class='fieldNameCell'>");
            out("                        <span id='" + fieldID + "'>");
            out("                           " + field.name());
            out("                        </span>");
            out("                        <div dojoType='dijit.Tooltip' connectId='" + fieldID + "'>");
            out("                           " + field.commentText());
            for(Tag tag : field.tags()) {
                out("                          <br/>" + tag.name() + " - " + tag.text());
             } // END tag loop
            out("                        </div>");
            out("                     </td>");
            out("                  </tr>");
         } // END field loop
         out("               </table>");
      } // END if not null
   } // END processFields static method

   /**
    * processMethods static method<br/>
    * This method outputs all the method information for a single java class, in pseudo-UML style.
    * @param methods An array of all the methods for a single java class
    */
   private static void processMethods(MethodDoc[] methods) {
      if(null != methods) {
         // Define the method table
         out("               <table cellpadding='1' cellspacing='0' class='methodTable'>");
         // If there methods
         if(methods.length > 0) {
            out("                  <tr align='left' valign='top' class='methodHeadingRow'>");
            out("                     <td colspan='2' class='methodHeadingCell'>");
            out("                        Methods");
            out("                     </td>");
            out("                  </tr>");
         } // END if there are methods

         // For each method
         TreeSet<MethodDoc> methodSet = new TreeSet<MethodDoc>();
         methodSet.addAll(Arrays.asList(methods));
         for(MethodDoc method : methodSet) {
            final String methodID = method.containingClass().name().replace(".", "") + "_" + method.name().replace(".", "") + "_" + method.flatSignature().replace(",", "_").replace(" ", "").replace("(", "").replace(")", "");
            out("                  <tr align='left' valign='top' class='methodRow'>");
            out("                     <td class='methodAccessCell'>");
            out("                        " + processModifiers(method));
            out("                     </td>");
            out("                     <td class='methodNameCell'>");
            out("                        <span id='" + methodID + "'>");
            out("                           " + method.name() + processParameters(method.parameters()));
            out("                           :");
            out("                           <span class='methodReturnTypeCell'>");
            out("                              " + method.returnType().typeName() + method.returnType().dimension());
            out("                           </span>");
            out("                        </span>");
            out("                        <div dojoType='dijit.Tooltip' connectId='" + methodID + "'>");
            out("                           " + method.commentText());
            for(Tag tag : method.tags()) {
                out("                          <br/>" + tag.name() + " - " + tag.text());
             } // END tag loop
            out("                        </div>");
            out("                     </td>");
            out("                  </tr>");
         } // END method loop
         out("               </table>");
      } // END if not null
   } // END processMethods static method

   /**
    * processConstructors static method<br/>
    * This method outputs all the constructor information for a single java class, in pseudo-UML style.
    * @param constructors An array of all the constructors for a single java class
    */
   private static void processConstructors(ConstructorDoc[] constructors) {
      if(null != constructors) {
         // Define the constructor table
         out("               <table cellpadding='1' cellspacing='0' class='constructorTable'>");
         // If there constructors
         if(constructors.length > 0) {
            out("                  <tr align='left' valign='top' class='constructorHeadingRow'>");
            out("                     <td colspan='2' class='constructorHeadingCell'>");
            out("                        Constructors");
            out("                     </td>");
            out("                  </tr>");
         } // END if there are constructors

         // For each constructor
         TreeSet<ConstructorDoc> constructorSet = new TreeSet<ConstructorDoc>();
         constructorSet.addAll(Arrays.asList(constructors));
         for(ConstructorDoc constructor : constructorSet) {
            final String constructorID = constructor.containingClass().name().replace(".", "") + "_" + constructor.name().replace(".", "") + "_" + constructor.flatSignature().replace(",", "_").replace(" ", "").replace("(", "").replace(")", "");
            out("                  <tr align='left' valign='top' class='constructorRow'>");
            out("                     <td class='constructorAccessCell'>");
            out("                        " + processModifiers(constructor));
            out("                     </td>");
            out("                     <td class='constructorNameCell'>");
            out("                        <span id='" + constructorID + "'>");
            out("                           " + constructor.name() + processParameters(constructor.parameters()));
            out("                        </span>");
            out("                        <div dojoType='dijit.Tooltip' connectId='" + constructorID + "'>");
            out("                           " + constructor.commentText());
            for(Tag tag : constructor.tags()) {
                out("                          <br/>" + tag.name() + " - " + tag.text());
             } // END tag loop
            out("                        </div>");
            out("                     </td>");
            out("                  </tr>");
         } // END constructor loop
         out("               </table>");
      } // END if not null
   } // END processConstructors static method

   /**
    * processModifiers static method<br/>
    * This method creates a string of all class member modifiers, including access, static and final modifiers.<br/>
    * The modifiers and their meanings are thus:<br/>
    *    + : public (UML standard)<br/>
    *    # : protected (UML standard)<br/>
    *    - : private (UML standard)<br/>
    *    ~ : package-private (UML standard)<br/>
    *    St : static (NOT UML standard)<br/>
    *    Fi : final (NOT UML standard)
    * @param ped A javadoc object which is the parent for fields, methods, constructors, etc.
    * @return String A string representation of all the modifiers for a class member.
    */
   private static String processModifiers(ProgramElementDoc ped) {
      if(null != ped) {
         StringBuilder strB = new StringBuilder();

         // Set access modifier
         if(ped.isPublic())              strB.append("+");
         else if(ped.isProtected())      strB.append("#");
         else if(ped.isPrivate())        strB.append("-");
         else if(ped.isPackagePrivate()) strB.append("~");
         else                            strB.append(" ");

         // Set static modifier
         if(ped.isStatic())              strB.append(" St");

         // Set final modifier
         if(ped.isFinal())               strB.append(" Fi");

         // Return all modifiers
         return strB.toString();
      } else {
         return " ";
      } // END if/else
   } // END processModifiers static method

   /**
    * processParameters static method<br/>
    * This method outputs information for all parameters in a parameter list.<br/>
    * Constructors and methods have parameter lists.
    * @param parameters An array of all the parameters for a single constructor or method
    * @return String A string representing all the parameter information in pseudo-UML style
    */
   private static String processParameters(Parameter[] parameters) {
      StringBuilder strB = new StringBuilder();

      if(null != parameters) {
         strB.append("<span class='bracket'>");
         strB.append("(");
         strB.append("</span>");

         // For each parameter
         for(int index = 0 ; index < parameters.length ; ++index) {
            Parameter parameter = parameters[index];

            strB.append("<br />\n                        ");
            strB.append("<span class='parameterCell'>");
            strB.append("<span class='parameterType'>");
            strB.append(parameter.type().typeName() + parameter.type().dimension());
            strB.append("</span>");
            strB.append(" ");
            strB.append("<span class='parameterName'>");
            strB.append(parameter.name());
            strB.append("</span>");
            strB.append("</span>");

            if(index < parameters.length - 1) strB.append(",");
            else if(index < parameters.length) strB.append("<br />" + Constants.NEW_LINE);
         } // END loop

         if(parameters.length > 0) strB.append("                     ");

         strB.append("<span class='bracket'>");
         strB.append(")");
         strB.append("</span>");
      } // END if not null

      return strB.toString();
   } // END processParameters static method

   /**
    * out static method<br/>
    * This is a convenience method for writing a single string to the file.<br/>
    * It also terminates the line and flushes the file buffer.
    * @param msg A single string to output to file
    */
   private static void out(String msg) {
      try {
         fout.write(msg);
         fout.newLine();
         fout.flush();
      } catch(NullPointerException npe) {
         System.out.println("Error: File Writer Is Undefined: " + npe.getMessage());
      } catch(IOException ioe) {
         System.out.println("Error: File Write Error: " + ioe.getMessage());
      } catch(Exception unknownExc) {
         System.out.println("Error: Unknown Error: " + unknownExc.getMessage());
      } // END try/catch
   } // END out static method

   /**
    * closeWriter static method<br/>
    * This method flushes and closes the file stream.
    * @param writer The file writer object
    */
   private static void closeWriter(Writer writer) {
      try {
         writer.flush();
         writer.close();
      } catch(NullPointerException npe) {
         System.out.println("Error: File Writer Is Undefined: " + npe.getMessage());
      } catch(IOException ioe) {
         System.out.println("Error: File Write Error: " + ioe.getMessage());
      } catch(Exception unknownExc) {
         System.out.println("Error: Unknown Error: " + unknownExc.getMessage());
      } // END try/catch
   } // END closeWriter static method
} // END DefaultDoclet class